<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>真实玻璃破碎效果</title>
    <script src="https://cdn.tailwindcss.com"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            overflow: hidden;
            height: 100vh;
        }

        #background {
            position: absolute;
            width: 100%;
            height: 100%;
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
            background-size: 400% 400%;
            animation: gradient 15s ease infinite;
        }

        @keyframes gradient {
            0% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }

        #overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            pointer-events: none;
            transition: opacity 0.3s ease;
        }

        #overlay-text {
            color: white;
            font-family: 'Arial', sans-serif;
            font-size: clamp(1.5rem, 5vw, 3rem);
            font-weight: bold;
            text-shadow: 0 0 10px rgba(0,0,0,0.5);
            text-align: center;
            padding: 20px;
        }

        canvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
        }

        #click-catcher {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 10;
        }

        #reset-btn {
            position: absolute;
            bottom: 20px;
            right: 20px;
            padding: 8px 16px;
            background-color: rgba(255,255,255,0.2);
            border: 1px solid rgba(255,255,255,0.3);
            border-radius: 4px;
            color: white;
            font-size: 14px;
            cursor: pointer;
            backdrop-filter: blur(5px);
            transition: all 0.3s ease;
            opacity: 0;
            pointer-events: none;
            z-index: 20;
        }

        #reset-btn.visible {
            opacity: 1;
            pointer-events: auto;
        }

        #reset-btn:hover {
            background-color: rgba(255,255,255,0.3);
        }
    </style>
</head>
<body>
<div id="background"></div>
<div id="overlay">
    <div id="overlay-text">点击任意位置<br>体验真实破碎效果</div>
</div>

<canvas id="fragments-canvas"></canvas>
<canvas id="cracks-canvas"></canvas>

<div id="click-catcher"></div>
<button id="reset-btn">重置</button>

<script>
    document.addEventListener('DOMContentLoaded', () => {
        // 获取元素
        const background = document.getElementById('background');
        const overlay = document.getElementById('overlay');
        const fragmentsCanvas = document.getElementById('fragments-canvas');
        const cracksCanvas = document.getElementById('cracks-canvas');
        const clickCatcher = document.getElementById('click-catcher');
        const resetBtn = document.getElementById('reset-btn');

        const fCtx = fragmentsCanvas.getContext('2d');
        const cCtx = cracksCanvas.getContext('2d');

        // 状态变量
        let fragments = [];
        let cracks = [];
        let isBroken = false;
        let animationFrameId = null;

        // 设置画布尺寸
        function resizeCanvases() {
            const width = window.innerWidth;
            const height = window.innerHeight;

            // 使用设备像素比优化高清显示
            const dpr = window.devicePixelRatio || 1;
            fragmentsCanvas.width = width * dpr;
            fragmentsCanvas.height = height * dpr;
            cracksCanvas.width = width * dpr;
            cracksCanvas.height = height * dpr;

            fragmentsCanvas.style.width = `${width}px`;
            fragmentsCanvas.style.height = `${height}px`;
            cracksCanvas.style.width = `${width}px`;
            cracksCanvas.style.height = `${height}px`;

            // 缩放上下文以匹配设备像素比
            fCtx.scale(dpr, dpr);
            cCtx.scale(dpr, dpr);

            // 重新绘制
            if (isBroken) {
                drawAll();
            }
        }

        // 初始化
        resizeCanvases();
        window.addEventListener('resize', resizeCanvases);

        // 生成破碎点
        function generateBreakPoints(centerX, centerY, count) {
            const points = [];
            const width = fragmentsCanvas.clientWidth;
            const height = fragmentsCanvas.clientHeight;

            // 确保中心附近点更密集
            for (let i = 0; i < count; i++) {
                // 距离因子：中心附近概率更高
                const distanceFactor = Math.random() * Math.random(); // 偏向于0
                const angle = Math.random() * Math.PI * 2;

                // 基础半径
                const maxRadius = Math.max(width, height) * 0.7;
                const radius = distanceFactor * maxRadius;

                // 计算位置
                let x = centerX + Math.cos(angle) * radius;
                let y = centerY + Math.sin(angle) * radius;

                // 确保在画布范围内
                x = Math.max(0, Math.min(width, x));
                y = Math.max(0, Math.min(height, y));

                // 添加随机扰动，使分布更自然
                x += (Math.random() - 0.5) * 40;
                y += (Math.random() - 0.5) * 40;

                points.push({x, y});
            }

            // 添加边角点，确保边缘完整
            points.push({x: 0, y: 0});
            points.push({x: width, y: 0});
            points.push({x: 0, y: height});
            points.push({x: width, y: height});
            points.push({x: width/2, y: 0});
            points.push({x: width, y: height/2});
            points.push({x: width/2, y: height});
            points.push({x: 0, y: height/2});

            return points;
        }

        // 计算Voronoi单元格（碎片）
        function computeFragments(points, centerX, centerY) {
            const width = fragmentsCanvas.clientWidth;
            const height = fragmentsCanvas.clientHeight;
            const fragments = [];

            // 为每个点计算单元格
            for (let i = 0; i < points.length; i++) {
                const point = points[i];
                const cell = {
                    center: {x: point.x, y: point.y},
                    vertices: [],
                    // 物理属性
                    velocity: {x: 0, y: 0},
                    rotation: 0,
                    rotationSpeed: 0,
                    opacity: 1,
                    // 缓存路径以提高性能
                    path: null
                };

                // 计算碎片到中心点的距离，用于物理效果
                const distToCenter = Math.hypot(point.x - centerX, point.y - centerY);
                const maxDist = Math.hypot(width, height);
                const impactFactor = 1 - (distToCenter / maxDist);

                // 基于距离的物理参数 - 离中心越近，受力越大
                const angle = Math.atan2(point.y - centerY, point.x - centerX) + (Math.random() - 0.5) * 0.8;
                const force = (0.8 + Math.random() * 1.2) * impactFactor;

                cell.velocity.x = Math.cos(angle) * force;
                cell.velocity.y = Math.sin(angle) * force;
                cell.rotationSpeed = (Math.random() - 0.5) * 0.03 * impactFactor;

                // 计算单元格边界（简化的Voronoi算法）
                const boundaryPoints = [];
                const step = 20; // 增大步长提高性能

                // 扫描画布边缘
                for (let x = 0; x <= width; x += step) {
                    for (let y = 0; y <= height; y += step) {
                        // 检查该点是否属于当前单元格
                        let isInside = true;
                        for (let j = 0; j < points.length; j++) {
                            if (i === j) continue;
                            if (distanceSquared(points[j], {x, y}) < distanceSquared(point, {x, y})) {
                                isInside = false;
                                break;
                            }
                        }

                        if (isInside) {
                            // 检查相邻点是否在外部，确定边界
                            let isBoundary = false;
                            for (let dx = -step; dx <= step; dx += step) {
                                for (let dy = -step; dy <= step; dy += step) {
                                    if (dx === 0 && dy === 0) continue;

                                    const nx = x + dx;
                                    const ny = y + dy;
                                    if (nx < 0 || nx > width || ny < 0 || ny > height) continue;

                                    let neighborInside = true;
                                    for (let j = 0; j < points.length; j++) {
                                        if (i === j) continue;
                                        if (distanceSquared(points[j], {x: nx, y: ny}) < distanceSquared(point, {x: nx, y: ny})) {
                                            neighborInside = false;
                                            break;
                                        }
                                    }

                                    if (!neighborInside) {
                                        boundaryPoints.push({x, y});
                                        isBoundary = true;
                                        break;
                                    }
                                }
                                if (isBoundary) break;
                            }
                        }
                    }
                }

                // 处理边界点，形成多边形
                if (boundaryPoints.length > 2) {
                    // 去重
                    const uniquePoints = [];
                    boundaryPoints.forEach(p => {
                        let duplicate = false;
                        for (const up of uniquePoints) {
                            if (distanceSquared(up, p) < step * step * 0.49) {
                                duplicate = true;
                                break;
                            }
                        }
                        if (!duplicate) uniquePoints.push(p);
                    });

                    // 按角度排序，形成闭合多边形
                    uniquePoints.sort((a, b) => {
                        const angleA = Math.atan2(a.y - point.y, a.x - point.x);
                        const angleB = Math.atan2(b.y - point.y, b.x - point.x);
                        return angleA - angleB;
                    });

                    // 添加随机扰动使边缘更不规则
                    const perturbedPoints = uniquePoints.map(p => {
                        const dist = Math.hypot(p.x - point.x, p.y - point.y);
                        const jitter = (Math.random() - 0.5) * Math.min(15, dist * 0.1);
                        return {
                            x: p.x + jitter * Math.cos(Math.atan2(p.y - point.y, p.x - point.x) + Math.PI/2),
                            y: p.y + jitter * Math.sin(Math.atan2(p.y - point.y, p.x - point.x) + Math.PI/2)
                        };
                    });

                    cell.vertices = perturbedPoints;

                    // 预先生成路径以提高绘制性能
                    const path = new Path2D();
                    path.moveTo(perturbedPoints[0].x, perturbedPoints[0].y);
                    perturbedPoints.forEach((v, idx) => {
                        if (idx === 0) return;
                        path.lineTo(v.x, v.y);
                    });
                    path.closePath();
                    cell.path = path;

                    fragments.push(cell);
                }
            }

            return fragments;
        }

        // 生成裂纹
        function generateCracks(fragments, centerX, centerY) {
            const cracks = [];
            const width = fragmentsCanvas.clientWidth;
            const height = fragmentsCanvas.clientHeight;

            // 1. 生成主裂纹（从中心向外辐射）
            const mainCrackCount = 6 + Math.floor(Math.random() * 3); // 减少裂纹数量提高性能
            for (let i = 0; i < mainCrackCount; i++) {
                const angle = (i / mainCrackCount) * Math.PI * 2 + (Math.random() - 0.5) * 0.3;
                const length = Math.max(width, height) * (0.6 + Math.random() * 0.4);

                const crackPoints = [
                    {x: centerX, y: centerY}
                ];

                // 生成主裂纹的点
                let currentX = centerX;
                let currentY = centerY;
                let currentAngle = angle;
                const segments = 4 + Math.floor(Math.random() * 5); // 减少段数

                for (let s = 0; s < segments; s++) {
                    const segmentLength = length / segments * (0.5 + Math.random());
                    currentAngle += (Math.random() - 0.5) * 0.4; // 轻微角度变化
                    currentX += Math.cos(currentAngle) * segmentLength;
                    currentY += Math.sin(currentAngle) * segmentLength;

                    // 确保在画布内
                    if (currentX < 0 || currentX > width || currentY < 0 || currentY > height) break;

                    crackPoints.push({x: currentX, y: currentY});
                }

                // 预生成裂纹路径
                const path = new Path2D();
                path.moveTo(crackPoints[0].x, crackPoints[0].y);

                for (let i = 1; i < crackPoints.length; i++) {
                    const p1 = crackPoints[i-1];
                    const p2 = crackPoints[i];
                    const cp = {
                        x: (p1.x + p2.x) / 2 + (Math.random() - 0.5) * 5,
                        y: (p1.y + p2.y) / 2 + (Math.random() - 0.5) * 5
                    };
                    path.quadraticCurveTo(cp.x, cp.y, p2.x, p2.y);
                }

                cracks.push({
                    path,
                    points: crackPoints,
                    width: 1.2 + Math.random() * 0.8,
                    opacity: 0.8 + Math.random() * 0.2,
                    isMain: true
                });
            }

            // 2. 生成次级裂纹（从主裂纹分支）
            cracks.forEach(mainCrack => {
                if (!mainCrack.isMain) return;

                const branchCount = 1 + Math.floor(Math.random() * 2); // 减少分支
                for (let i = 0; i < branchCount; i++) {
                    // 在主裂纹的随机点分支
                    const pointIndex = 1 + Math.floor(Math.random() * (mainCrack.points.length - 2));
                    const startPoint = mainCrack.points[pointIndex];

                    // 计算分支角度（与主裂纹成一定角度）
                    const prevPoint = mainCrack.points[pointIndex - 1];
                    const nextPoint = mainCrack.points[pointIndex + 1];
                    const mainAngle = Math.atan2(nextPoint.y - prevPoint.y, nextPoint.x - prevPoint.x);
                    const branchAngle = mainAngle + (Math.random() > 0.5 ? 1 : -1) * (Math.PI/4 + Math.random() * Math.PI/4);

                    // 生成分支裂纹
                    const branchPoints = [startPoint];
                    let currentX = startPoint.x;
                    let currentY = startPoint.y;
                    let currentAngle = branchAngle;
                    const length = 40 + Math.random() * 100;
                    const segments = 2 + Math.floor(Math.random() * 3); // 减少段数

                    for (let s = 0; s < segments; s++) {
                        const segmentLength = length / segments * (0.5 + Math.random());
                        currentAngle += (Math.random() - 0.5) * 0.6;
                        currentX += Math.cos(currentAngle) * segmentLength;
                        currentY += Math.sin(currentAngle) * segmentLength;

                        if (currentX < 0 || currentX > width || currentY < 0 || currentY > height) break;

                        branchPoints.push({x: currentX, y: currentY});
                    }

                    // 预生成路径
                    const path = new Path2D();
                    path.moveTo(branchPoints[0].x, branchPoints[0].y);

                    for (let i = 1; i < branchPoints.length; i++) {
                        const p1 = branchPoints[i-1];
                        const p2 = branchPoints[i];
                        const cp = {
                            x: (p1.x + p2.x) / 2 + (Math.random() - 0.5) * 5,
                            y: (p1.y + p2.y) / 2 + (Math.random() - 0.5) * 5
                        };
                        path.quadraticCurveTo(cp.x, cp.y, p2.x, p2.y);
                    }

                    cracks.push({
                        path,
                        points: branchPoints,
                        width: 0.6 + Math.random() * 0.5,
                        opacity: 0.6 + Math.random() * 0.2,
                        isMain: false
                    });
                }
            });

            return cracks;
        }

        // 距离计算（使用平方距离提高性能）
        function distanceSquared(p1, p2) {
            const dx = p1.x - p2.x;
            const dy = p1.y - p2.y;
            return dx * dx + dy * dy;
        }

        // 绘制碎片
        function drawFragments() {
            const width = fragmentsCanvas.clientWidth;
            const height = fragmentsCanvas.clientHeight;
            fCtx.clearRect(0, 0, width, height);

            fragments.forEach(fragment => {
                if (!fragment.path || fragment.vertices.length < 3) return;

                fCtx.save();

                // 应用旋转和位移
                fCtx.translate(fragment.center.x, fragment.center.y);
                fCtx.rotate(fragment.rotation);
                fCtx.translate(-fragment.center.x, -fragment.center.y);

                // 使用预生成的路径
                fCtx.beginPath();
                fCtx.addPath(fragment.path);

                // 裁剪并绘制背景色（使用渐变模拟玻璃反光）
                fCtx.clip();
                fCtx.globalAlpha = fragment.opacity;

                // 为碎片添加独特的渐变，模拟玻璃折射效果
                const gradient = fCtx.createLinearGradient(
                    fragment.center.x - 50, fragment.center.y - 50,
                    fragment.center.x + 50, fragment.center.y + 50
                );

                // 基于碎片位置生成不同的渐变颜色
                const baseHue = (Math.sin(fragment.center.x * 0.01) * 0.5 + 0.5) * 360;
                gradient.addColorStop(0, `hsla(${baseHue}, 70%, 60%, 0.9)`);
                gradient.addColorStop(1, `hsla(${(baseHue + 30) % 360}, 70%, 45%, 0.9)`);

                fCtx.fillStyle = gradient;
                fCtx.fillRect(0, 0, width, height);

                // 绘制碎片边缘高光
                fCtx.globalAlpha = 0.3;
                fCtx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
                fCtx.lineWidth = 1.2;
                fCtx.stroke();

                fCtx.restore();
            });
        }

        // 绘制裂纹
        function drawCracks() {
            const width = cracksCanvas.clientWidth;
            const height = cracksCanvas.clientHeight;
            cCtx.clearRect(0, 0, width, height);

            cracks.forEach(crack => {
                if (!crack.path || crack.points.length < 2) return;

                cCtx.save();
                cCtx.beginPath();
                cCtx.addPath(crack.path);

                // 绘制裂纹主体
                cCtx.strokeStyle = `rgba(255, 255, 255, ${crack.opacity * 0.7})`;
                cCtx.lineWidth = crack.width;
                cCtx.stroke();

                // 绘制裂纹高光
                cCtx.strokeStyle = `rgba(255, 255, 255, ${crack.opacity * 0.3})`;
                cCtx.lineWidth = crack.width + 1.5;
                cCtx.stroke();
                cCtx.restore();
            });
        }

        // 绘制所有元素
        function drawAll() {
            drawFragments();
            drawCracks();
        }

        // 碎片动画更新
        function updateFragments(deltaTime) {
            const damping = 0.97; // 阻尼系数，模拟空气阻力

            for (let i = 0; i < fragments.length; i++) {
                const fragment = fragments[i];

                // 更新位置
                fragment.center.x += fragment.velocity.x * deltaTime;
                fragment.center.y += fragment.velocity.y * deltaTime;

                // 应用阻尼
                fragment.velocity.x *= damping;
                fragment.velocity.y *= damping;

                // 更新旋转
                fragment.rotation += fragment.rotationSpeed * deltaTime;
                fragment.rotationSpeed *= damping;
            }
        }

        // 破碎动画
        function animateBreak() {
            let lastTime = performance.now();
            const duration = 1200; // 动画持续时间
            const startTime = performance.now();

            function animate(currentTime) {
                // 取消之前的动画帧以防累积
                if (animationFrameId) {
                    cancelAnimationFrame(animationFrameId);
                }

                const deltaTime = (currentTime - lastTime) / 16.67; // 标准化到60fps
                lastTime = currentTime;

                const progress = Math.min((currentTime - startTime) / duration, 1);

                // 更新碎片物理状态
                updateFragments(deltaTime);

                // 绘制
                drawAll();

                // 继续动画
                if (progress < 1) {
                    animationFrameId = requestAnimationFrame(animate);
                } else {
                    // 动画结束
                    resetBtn.classList.add('visible');
                    animationFrameId = null;
                }
            }

            animationFrameId = requestAnimationFrame(animate);
        }

        // 触发破碎
        function breakScreen(x, y) {
            // 隐藏文字
            overlay.style.opacity = 0;

            // 生成破碎点（减少数量提高性能）
            const pointCount = 40 + Math.floor(Math.random() * 15);
            const points = generateBreakPoints(x, y, pointCount);

            // 生成碎片和裂纹
            fragments = computeFragments(points, x, y);
            cracks = generateCracks(fragments, x, y);

            // 开始动画
            isBroken = true;
            animateBreak();
        }

        // 重置
        function reset() {
            // 取消动画
            if (animationFrameId) {
                cancelAnimationFrame(animationFrameId);
                animationFrameId = null;
            }

            // 清空画布
            const width = fragmentsCanvas.clientWidth;
            const height = fragmentsCanvas.clientHeight;
            fCtx.clearRect(0, 0, width, height);
            cCtx.clearRect(0, 0, width, height);

            // 重置状态
            fragments = [];
            cracks = [];
            isBroken = false;
            resetBtn.classList.remove('visible');

            // 显示文字
            overlay.style.opacity = 1;
        }

        // 事件监听
        clickCatcher.addEventListener('click', (e) => {
            if (!isBroken) {
                breakScreen(e.clientX, e.clientY);
            }
        });

        resetBtn.addEventListener('click', reset);
    });
</script>
</body>
</html>